E-api 서버 감사
개요
E-쿠버네티스 인증 실습을 본격적으로 하기 이전에, 어떻게 이를 모니터링할 수 있을지를 보자.
kube-apiserver에 발생하는 각종 이벤트를 로깅해보자.
즉, 감사를 한번 세팅해본다.
감사에는 여러 스테이지가 존재한다.
RequestReceived
- 감사 핸들러가 요청을 받았을 때 발생하는 스테이지로, 어떤 요청이든 이건 발생하기 마련이다.ResponseStarted
- 응답 헤더가 보내질 때 발생하는 스테이지로, watch와 같이 오래 이어지는 요청에 대해서만 발생한다.ResponseComplete
- 응답 바디가 전부 보내진 이후 발생하는 스테이지.Panic
- 패닉이 발생했을 때 생기는 이벤트 스테이지.
감사 정책
위의 스테이지들에 대해서 정책을 설정할 수 있는데, 다음의 레벨을 지정할 수 있다.
None
- 규칙에 매칭되지 않으면 이벤트를 로깅하지 않는다.Metadata
- 요청을 로깅하긴 하나, 내용은 제외하고 user, timestamp, resource, verb 등의 메타데이터를 로깅한다.Request
- 요청의 전체 내용을 로깅한다.RequestResponse
- 요청과 요청에 대한 응답이 어떻게 됐는지까지 모든 내용을 로깅한다.
- 위로 따지면 Response 스테이지의 내용들까지 전부 가게 되는 것이다.
누가 요청을 했는지까지만 중요하다면 Metadata, 해당 요청의 내용까지 궁금하면 Request를 쓰면 된다.
해당 요청에 대해 어떤 응답이 나갔는지까지 궁금하다면, RequestResponse를 쓰면 되는데 이거 넣으면 내용이 장난 없이 길어진다.
그래서 필요에 따라 적절하게 사용하는 것이 필요하겠다.
아래 둘은 리소스가 아닌 요청에 대해서는 로깅되지 않는다는데, 확인해보자.
apiVersion: audit.k8s.io/v1
kind: Policy
# 이벤트를 만들지 않을 스테이지를 넣어주면 된다.
omitStages:
- "RequestReceived"
rules:
# Log pod changes at RequestResponse level
- level: RequestResponse
resources:
- group: ""
resources: ["pods"]
- level: Metadata
resources:
- group: ""
resources: ["pods/log", "pods/status"]
- level: None
resources:
- group: ""
resources: ["configmaps"]
resourceNames: ["controller-leader"]
- level: None
users: ["system:kube-proxy"]
verbs: ["watch"]
resources:
- group: "" # core API group
resources: ["endpoints", "services"]
# Don't log authenticated requests to certain non-resource URL paths.
- level: None
userGroups: ["system:authenticated"]
nonResourceURLs:
- "/api*" # Wildcard matching.
- "/version"
- level: Request
resources:
- group: "" # core API group
resources: ["configmaps"]
namespaces: ["kube-system"]
- level: Metadata
resources:
- group: ""
resources: ["secrets", "configmaps"]
- level: Request
resources:
- group: "" # core API group
- group: "extensions" # Version of group should NOT be included.
- level: Metadata
omitStages:
- "RequestReceived"
이런 식으로 써주면 된다.
나는 이런 식으로, 인증에 필요한 이벤트만 볼 수 있도록 설정했다.
중요한 점 중 하나는 바로 규칙이 순서대로 적용된다는 것이다.
어떤 조건에 먼저 매칭이 되버리면 그 친구는 바로 해당 조건에 따라 적용되어 버린다.
예를 들어서 내가 하나의 서비스 어카운트의 이벤트만 받고 싶다고 생각해보자.
그러면 다른 서비스 어카운트는 전부 None으로 설정하고 싶을 것이다.
이에 대해 이런 식으로 정책을 설정할 수 있다.
system:serviceaccount:{네임스페이스}:{내 서비스 어카운트}
유저에 대해 Request로 레벨을 설정한다.- 그리고 다음에
system:serviceaccounts
그룹에 대해 None 레벨을 설정한다.
이렇게 하면 원하는대로 동작을 하게 된다.
왜냐? 내가 원하는 서비스 어카운트는 첫 규칙에 적용되어 이벤트가 감사된다.
그리고 다른 서비스 어카운트는 첫 규칙에 해당하지 않기 때문에 두번째 규칙에 걸려 이벤트가 기록되지 않는다.
그러나 만약 두 규칙의 순서를 바꾸게 된다면, 내가 원하는 서비스 어카운트도 첫 규칙에 None으로 걸려버려서 결과를 받을 수 없게 되는 것이다.
이벤트 배치
이벤트는 굉장히 많이 발생할 것이다.
이때 이 이벤트들을 어떻게 보낼지를 정한다.
배치 처리해서 모아서 보내는 것도 가능하고, 모든 이벤트를 매번 보내는 것도 가능하다.
--audit-webhook-mode
에 설정을 해주면 된다.batch
- 기본값으로, 이벤트를 비동기적으로 배치 처리하기 이전에 버퍼에 저장한다.blocking
- 각 이벤트 처리에 대해 응답은 블록된다.blocking-strict
- blocking과 같으나, RequestedReceived 스테이지에서 감사 로깅이 실패하면 전체 요청이 실패한다.
batch 모드일 때는 추가 설정을 넣어줄 수 있다.
--audit-webhook-batch-buffer-size
배치 처리하기 이전 저장할 버퍼의 크기로, 들어오는 버퍼 크기를 초과한 이벤트는 버려진다.--audit-webhook-batch-max-size
한 배치에 들어갈 이벤트 최대 개수--audit-webhook-batch-max-wait
큐의 이벤트를 배치 처리할 때 최대 기다릴 시간.--audit-webhook-batch-throttle-qps
평균 1초에 발생할 배치의 최대값?--audit-webhook-batch-throttle-burst
QPS가 없을 때 같은 순간에 최대로 생길 배치 수
로컬 파일시스템에 저장하기
가장 기본적인 방법이라고 할 수 있겠다.
말 그대로 파일시스템을 마운팅해 거기에 모든 이벤트를 저장하는 것이다.
흔히 리눅스에서 사용되는 그냥 /var/log
경로를 이용해보겠다.
일단 어떤 것들을 감사할지 정책 파일을 정의한다.
이렇게 하면 그냥 모든 것들을 남기겠다는 것이다.
일단 세팅에 성공하고 나서, 조금 더 구체적으로 필요한 이벤트만 남기도록 하자.
이렇게, 마스터 노드의 정적 파드의 경로로 가서 api 서버를 세팅해주면 알아서 설정된다.
정책 파일과, 로그를 저장할 파일을 지정하면 된다.
간단하게 세팅하는 것이 목적이기 때문에, hostpath를 활용했다.
기다리다보면 알아서 api 서버가 재시작되면서 세팅된다.
와.. 아주 잠깐 세팅했을 뿐인데, 순식간에 11메가를 넘겼다 ㅋㅋ
가장 마지막에 남은 이벤트만 한번 봤다.
이제 알았는데, 논 리소스에 대한 요청은 그냥 전부 익명으로 받아들여지나..?
함부로 익명 유저 거부했다간 난리나는 수가 있겠다.
이것도 나중에 따로 테스트해봐야겠다.
아무튼 이벤트가 잘 담기는 것이 확인된다.
이제 본격적으로 어떤 것들을 로깅할지 구체적으로 특정해본다.
내가 생각한 유저와, 서비스 어카운트에 대해서만 모든 요청을 로깅하는 식으로 해보겠다.
정책 파일을 수정하고 그냥 기다려봤는데, 감사 관련 설정에 대해서는 동적으로 추적을 해주지 않는 듯하다.
귀찮지만 정책파일을 바꿀 때마다 api서버도 살짝 수정해준다.
요지는, 이 유저가 발생시키는 모든 이벤트를 나오게 하면 된다.
음.. 생각대로 나와주지 않는다.
흉내는 인증이 완료된 이후 유저 정보를 흉내한 계정으로 변경한다고 했다.
이에 따라 흉내를 통한 요청은 로깅이 됐어야 했다.
라고 생각을 했으나, 그렇게 동작하지 않네..
그냥 kubernetes-admin으로 들어오는 듯하다.
정답은 여기에 있었다.
cat audit.log | jq 'select(.user.username == "kubernetes-admin")'
그냥 impersonatedUser라 하여 나오는 구나..
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Request
users: ["test-ua", "test-sa", "kubernetes-admin"]
namspaces: ["default"]
그래서 이번에는 이렇게 현재 내 kubectl에 설정된 유저까지 포함하도록 설정했다.
근데 막상 받아보니, RequestResponse
는 진짜 자질구레한 내용이 다 나온다.
내 요청이 수행되어 어떤 내용물이 반환되었는지 알 필요는 없다.
하지만 어떤 요청애 동작했는지까지는 알고 싶기 때문에 레벨은 Request로 다시 설정했다.
또한 나는 default 네임스페이스에 대한 동작만 확인할 것이기 때문에 관련한 설정도 넣어줬다.
웹훅으로 원격 서비스에 날리기
원격 서버에서 이벤트를 받을 수도 있다.
원격 서버 초기 세팅
이를 위해 먼저 간단하게 웹 서버를 만들어준다.
아무래도 내가 빠르게 다룰 수 있는 친숙한 FastAPI를 활용했다.
환경 세팅은 Poetry를 활용했다.
poetry init
poetry add fastapi
poetry add uvicorn
이렇게 설치하고..
[tool.pyright]
venvPath = "."
venv = ".venv"
pyright에서 가상환경 경로를 제대로 인식하게 하려면 이걸 pyproject.toml에 넣어준다.
poetry run uvicorn main:app
정말 간단하게 만들었다.
네트워크 설정
이 서버는 내 호스트에 있고, 나는 Vagrant를 통해 클러스터를 구축했기에 vm에서 호스트로 통신을 해야 한다.
private network를 설정했다면, vm과 호스트 사이에는 다음의 인터페이스가 연결돼있다.
이게 호스트의 인터페이스
이건 vm에서의 인터페이스이다.
사실 vm에서 나가는 모든 트래픽이 결국 호스트의 인터페이스를 탈 거니까 이를 추적할 수 있을 거라 생각했는데, virtualbox nat 모드는 가상 라우터가 내부에 있기에 원하는 ip를 제대로 추적하기 힘들 수도 있다고 한다.
계속 디버깅하면 가능하겠지만, 일단 지금의 과제에 부합한 탐구는 아니라 여기에서는 생략한다.
아무튼 해당 인터페이스에서의 호스트 주소에 서버를 바인딩했다.
그러나, 이렇게만 해서는 ping은 되더라도 tcp 연결이 제대로 되지 않는다.
tcpdump로 보면 syn 요청이 가나 ack가 돌아가질 않는다.
이건 그냥 단순하게 내가 방화벽 설정을 안 해놔서 그런 거였다.
sudo ufw allow 8000/tcp
이제 통신이 가능하다.
tls를 위한 인증서 발급
이제 https 설정을 하고, 서로 인증할 수 있도록 설정하자.
기본적인 fastapi 세팅은 redirect를 하도록 해주면 된다.[2]
이 다음에는 uvicorn 단에서 ssl 설정 파일을 넣어주는 작업을 하면 된다.
그럼 어떤 인증서를 써야 하는가?
api서버와 문제 없이 통신하기 위해서는 같은 root ca에 서명을 받은 인증서를 새로 만들면 될 것 같다.
이게 api서버가 활용하는 ca 인증서다.
kubeconfig 파일에도 base64로 인코딩된 채 그대로 들어있다.
웹훅 서버 인증서 발급
과거에도 해본 경험은 있지만, 그때는 인터넷 글을 복붙하는 수준이었고 이번에는 조금 더 확실하게 이해해가며 진행해보자.
openssl을 사용할 것이며 다음의 과정을 거칠 것이다.
- 개인키 생성
- 인증서를 받을 때 공개키에 대해서 받아야 한다고 생각해서 헷갈렸는데, 보통 툴의 개인키에는 n,d 등 rsa에서 계산을 위한 값이 포함되어 있어 공개키를 쉽게 추출해낼 수 있다.
- 인증서 서명 요청(Certificate Signing Request)
- 개인키를 넣어서 인증서를 요청 파일을 만든다.
- 알아서 공개키를 정보를 추출해낼 수 있기에 개인키만 넣는 것이다.
- root CA를 통해 인증서 발급
- 클러스터에서 사용되는 CA를 이용해서 진행한다.
# rsa 방식으로 2048 비트의 개인키 생성
openssl genrsa -out webhook.key 2048
# 해당 키를 까보고 싶으면 이렇게 하면 정보를 볼 수 있다.
openssl rsa -in webhook.key -text -noout
# 굳이 공개키를 직접 꺼내보고 싶다면
openssl rsa -in webhook.key -pubout -out webhook.pub
# CSR 발행, CN은 꼭 넣어줘야 한다.
openssl req -new -key webhook.key -out webhook.csr
# CSR 내용 확인
openssl req -in webhook.csr -text -noout
# 혹시, 자체 서명을 한 root 인증서로 쓰고 싶다면 이렇게
openssl req -x509 -new -key rootca.key -out rootca.crt
# 인증서 발급
openssl x509 -req -in webhook.csr -CA ca.crt -CAkey ca.key -out webhook.crt
# 인증서 내용 보기
openssl x509 -in webhook.crt -noout -text
# 제대로 서명됐는지 검증
openssl verify -CAfile ca.crt webhook.crt
활용할 수 있는 기본 명령어들이다.
솔직히 조금 불친절한 툴이라 생각한다..
curl --cacert ca.crt https://webhook.com:8000
tls가 잘되는지 확인은 이렇게 한다.
이건 실제로 잘 되는 게 확인됐다.
curl --cacert ca.crt --cert apiserver.crt --key apiserver.key https://webhook.com:8000
mtls는 이렇게 확인하면 된다.
api 서버 세팅
다음으로 해줄 것은 이렇게 api 서버 인자를 수정하는 것이다.
apiVersion: v1
kind: Config
# remote service
clusters:
- name: audit-webhook
cluster:
certificate-authority: /etc/kubernetes/pki/ca.crt
server: https://webhook.com:8000/audit
preferences: {}
# api server
users:
- name: api-server
user:
client-certificate: /etc/kubernetes/pki/apiserver.crt
client-key: /etc/kubernetes/pki/apiserver.key
current-context: audit@kubernetes
contexts:
- context:
cluster: audit-webhook
user: api-server
name: audit@kubernetes
config 파일은 이렇게 kubeconfig 방식으로 구성하면 된다.
이 방식은 향후 웹훅 관련 기능을 이용할 때 전부 공통적으로 사용하는 방식이므로, 방식을 미리 알아두는 것이 좋다.
원격 서버가 제대로 된 서버인지 확인하기 위해 사용할 내 ca 파일은 당연히 클러스터 ca여야 할 것이다.
반대로 api서버가 누구인지를 인증하는 키와 인증서도 넣어주었다.
이게 나중에 어떻게 활용되는지 꼭 보도록 하자.
트러블 슈팅
crictl logs $(crictl ps | grep apiserver | awk '{print $1}') 2>&1 | grep webhook
crictl을 이용해서 현재 api 서버 컨테이너에 어떤 출력이 일어나고 있는지 확인할 수 있다.
제대로 api서버가 기동되지 않아 확인해보니 데이터를 잘못 넣고 있었다.
-data
로 필드를 넣을 때는 base64로 인코딩된 키를 넣어야 한다.
그러나 그냥 파일을 명시하고 싶다면 -data
는 사용하면 안 된다.
(참고로 위의 코드는 이미 수정을 거친 결과물이다.)
I0122 06:00:56.414222 1 server_cert_deprecations.go:68] missing-san.invalid-cert.kubernetes.io: invalid certificate detected connecting to "webhook.com": relies on a legacy Common Name field instead of the SAN extension for subject validation
다음에는 이런 에러가 발생한다.
웹훅 서버로 요청이 가지 않았고, 그 이전에 api 서버 단에서 이미 문제가 발생한 것으로 보인다.
아니다.
tcpdump로 확인해보니 요청은 이미 잘만 가고 있었다.
다만 웹훅 서버의 로그로 뜨지 않은 것은, tls 인증이 실패하여 결국 핵심 요청 수행 로직이 발동되지 않았기 때문일 것이다.
CN은 이제 안 쓰는 건가..? 찾아보니 20년도 더 된 방식이라고 한다.
아무튼 SAN(Subject Alt Name)을 따로 채워서 인증서를 요청해야 하나 보다.[3]
openssl req -new -key webhook.key \
-subj "/CN=webhook.com" \
-addext "subjectAltName = DNS:webhook.com" \
-out webhook.csr
csr을 만들 때부터 일단 넣어줘야 한다.
이제는 되려나?
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
req_extensions = my_ext
[req_distinguished_name]
CN = webhook.com
C = KR
L = Seoul
O = practice
[my_ext]
basicConstraints = CA:FALSE
extendedKeyUsage = clientAuth, serverAuth
keyUsage = critical, digitalSignature, keyEncipherment
nsCertType = client
subjectAltName = @alt_names
[alt_names]
DNS.0 = webhook.com
DNS.0 = www.webhook.com
IP.0 = 192.168.80.1
그러나 제대로 안 돼서, config 파일을 이용해서 다시 설정해보려고 한다.
처음에 했던 것 보다 더 많이 수정을 가했다.
아래 삽질 열심히 하다가, 쿠버네티스를 자체 세팅할 때 사용하는 발급 방식을 조금 참고해보면 좋겠단 생각이 들어서 그것을 참고했다.[4]
openssl req -new -key webhook.key -out webhook.csr -config webhook.cnf -section req
openssl x509 -req -in webhook.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out webhook.crt
이번에는 config 파일 경로를 전달한 후, 해당 파일의 어느 섹션을 이용할 것인지 적어넣었다.
이렇게 하지 그냥 터미널을 통해서 할 수 있는 방법도 존재한다.
일단 csr에서는 확실히 값이 들어가는 것을 확인했다.
분명히 SAN이 들어가고는 있는데, 왜 계속 같은 에러가 날까?
openssl x509 -req -extfile <(printf "subjectAltName=DNS:webhook.com,IP:192.168.80.1") -in webhook.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out webhook.crt
여러 사이트와 gpt를 이용해서 찾아보며, 다른 방법으로도 시도했다.
이건 인증서를 만드는 시점에 값을 넣는 방식이라는 점이 조금 다르다고 할 수 있을 것 같다.
슬슬 어지럽네..[5]
아직 ssl툴 사용이 익숙치 않아 헤맸는데, 뽑혀 나온 인증서 정보를 뜯어봐야 확실하게 값이 들어갔는지 알 수 있는 거라 할 수 있겠다.
왜인지는 아직 잘 모르겠지만, 인증서 발급 시점에 cli로 넣지 않고 그냥 인증서를 뽑으면 제대로 SAN 값이 반영되지 않았다.
안 쉬고 계속 붙들고 있다가 혼미해져서 착각한 것일 수도 있어서, 다음에 이건 다시 확인해봐야 할 듯하다.
내가 조금 헤맸던 부분 중 또 한 지점이 웹훅 서버 재가동이었다.
uvicorn은 디렉토리의 변경을 읽고 서버를 알아서 리로딩시켜주지만, 자신 자체의 변화를 감지하지는 않는다는 것을 내가 간과했다.
아무튼, 어마무시한 시간이 걸려서 결국 요청을 받아내는데 성공했다!
원격 서버 데이터 보기
이제 default 네임스페이스로 가는 모든 요청은 감사된다.
간단하게 보내진 데이터를 한번 살펴보자.
Headers({'host': 'webhook.com:8000', 'user-agent': 'Go-http-client/1.1', 'content-length': '1675', 'accept': 'application/json, */*', 'content-type': 'application/json', 'referer': 'https://webhook.com:8000/audit?timeout=30s', 'accept-encoding': 'gzip'})
들어온 헤더는 이렇게 구성돼있다.
사실 딱히 볼 만한 구석은 없는 것 같다.
딱히 커스텀해서 들어오는 필드가 없다.
{'apiVersion': 'audit.k8s.io/v1',
'items': [{'auditID': 'f53496b9-d126-4b91-9010-212c9a0aeed4',
'level': 'Request',
'objectRef': {'apiVersion': 'v1',
'namespace': 'default',
'resource': 'pods'},
'requestReceivedTimestamp': '2025-01-22T13:48:55.964670Z',
'requestURI': '/api/v1/namespaces/default/pods?limit=500',
'sourceIPs': ['192.168.80.1'],
'stage': 'RequestReceived',
'stageTimestamp': '2025-01-22T13:48:55.964670Z',
'user': {'extra': {'authentication.kubernetes.io/credential-id': ['X509SHA256=e7fcded028b20c07994cdf828460bdfa54892200e31ce4f13eb35862a887525c']},
'groups': ['kubeadm:cluster-admins',
'system:authenticated'],
'username': 'kubernetes-admin'},
'userAgent': 'kubectl/v1.30.3 (linux/amd64) kubernetes/6fc0a69',
'verb': 'list'},
{'annotations': {'authorization.k8s.io/decision': 'allow',
'authorization.k8s.io/reason': 'RBAC: allowed by '
'ClusterRoleBinding '
'"kubeadm:cluster-admins" '
'of ClusterRole '
'"cluster-admin" to '
'Group '
'"kubeadm:cluster-admins"'},
'auditID': 'f53496b9-d126-4b91-9010-212c9a0aeed4',
'level': 'Request',
'objectRef': {'apiVersion': 'v1',
'namespace': 'default',
'resource': 'pods'},
'requestReceivedTimestamp': '2025-01-22T13:48:55.964670Z',
'requestURI': '/api/v1/namespaces/default/pods?limit=500',
'responseStatus': {'code': 200, 'metadata': {}},
'sourceIPs': ['192.168.80.1'],
'stage': 'ResponseComplete',
'stageTimestamp': '2025-01-22T13:48:55.966575Z',
'user': {'extra': {'authentication.kubernetes.io/credential-id': ['X509SHA256=e7fcded028b20c07994cdf828460bdfa54892200e31ce4f13eb35862a887525c']},
'groups': ['kubeadm:cluster-admins',
'system:authenticated'],
'username': 'kubernetes-admin'},
'userAgent': 'kubectl/v1.30.3 (linux/amd64) kubernetes/6fc0a69',
'verb': 'list'}],
'kind': 'EventList',
'metadata': {}}
이게 바디로 들어온 데이터로, 일단 이벤트 리스트를 보낸다.
이 안 속에 위에서 봤던 json 데이터가 고대로 들어가 있다.
--audit-webhook-mode
를 Batch로 해두니 이벤트를 받는데 시간 지연이 조금 길었다.
그래서 Blocking으로 설정했다.
결론
api 서버 이벤트 감사를 받아보는 두 가지 방법에 대해 알 수 있었다.
이제 인증이 제대로 되는지 확인하기 위한 준비 단계를 마쳤다!
그럼 이 두 가지 중에 무슨 방법을 쓸 것인가?
사실 테스트 목적에서는 고민할 것도 없이 그냥 파일시스템에 저장하는 게 비용적으로 이득이라고 생각한다.
웹서버를 따로 띄워야 하고 트래픽이 추가 발생하기도 하고.
그래서 힘들게 구축 실습을 하기는 했으나.. 다음 웹훅을 사용하기 전까지 웹훅은 세이프!
로컬에 저장하는 것도 조금은 아쉬운 게, 한 파일에 데이터가 계속 쌓인다.
리셋하겠답시고 파일을 지워도 파일을 새로 만들어주지 않더라..
그래서 조금 사용해보고 불편한 점이 발견되면 설정이 전부 남아있으니 그대로 웹훅으로 활용하련다.
험난한 mtls 도전기..
웹훅 서버 설정에서 꽤나 애를 먹었는데, 인증서 처리가 제대로 되지 않았었기 때문이다.
첫번째로 어려웠던 것은 어디에서 문제가 발생하는 것인지 빠르게 캐치하는 것이 힘들었다는 것이다.
kubeconfig 파일을 직접 만지는 것도 처음이었고, ssl 인증서는 let's encrypt로 딸깍 받아만 오다 보니 명령어에 익숙하지 않아 한참 헤매기도 했다.
이 부분은 아직 내가 미숙했으나, 장차 인증 웹훅과 어드미션 웹훅을 할 때는 조금 더 빠르게 해낼 수 있을 것 같다.
openssl이 아무래도 오래됐고, 내가 잘 모르는 영역에 처음 들어갈 때는 기본부터 닦는 게 중요하다고 생각해서 시도했었다.
그러나 막상 해보니, 명령어를 진짜 일일히 치고 있자니 너무 귀찮았다.
또한 구글링을 해도 생각보다 친절하게 설명된 글을 찾기 힘들었다.
쿠버네티스 문서에서는 cfssl을 통해 tls를 관리하는 예제를 많이 가지고 있다.
설정 파일을 간단하게 설정해 대량 인증서 발급에 유리하다고 하니, 다음에는 cfssl을 사용해볼 것 같다.
참고
https://kubernetes.io/docs/reference/config-api/apiserver-config.v1/ ↩︎
https://fastapi.tiangolo.com/advanced/middleware/#integrated-middlewares ↩︎
https://stackoverflow.com/questions/64814173/how-do-i-use-sans-with-openssl-instead-of-common-name ↩︎
https://github.com/kelseyhightower/kubernetes-the-hard-way/blob/master/ca.conf ↩︎
https://serverfault.com/questions/779475/openssl-add-subject-alternate-name-san-when-signing-with-ca ↩︎